/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;

import org.zhiqim.httpd.HttpServer;
import org.zhiqim.httpd.model.HttpSslConfig;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Closes;

/**
 * HTTPS非阻塞式监听器<br>
 * 1、继承自HttpNioSslListener
 * 2、重写getScheme方法，返回结果HTTPS
 * 3、线程开启前创建SSLContext
 * 4、重写newConnection增加SSLEngine参数
 * 
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 * @version v1.5.0 @author zouzhigang 2018-11-24 修改为完成从HttpNioListener继承，优化重写的方法
 */
public class HttpNioSslListener extends HttpNioListener
{
    private final HttpSslConfig ssl;
    private SSLContext sslContext;
    
    /** 构造函数，初始新建SSL配置类 */
    public HttpNioSslListener(HttpServer server)
    {
        super(server);
        this.ssl = new HttpSslConfig();
    }
    
    @Override /** 协议 */
    public String getScheme()
    {
        return _HTTPS_;
    }
    
    @Override /** 线程开启前 */
    protected void openBeforeInner()
    {
        try
        {
            //优先SSL参数（五项，其中Keystore两项必须）
            ssl.initParam(listenerGroup);
            sslContext = ssl.createSSLContext();
        }
        catch(Exception e)
        {
            throw Asserts.exception(e);
        }
    }
    
    /** 接受客户端连接 */
    protected void onAccept(SelectionKey key)
    {
        ServerSocketChannel server = (ServerSocketChannel)key.channel();
        
        HttpNioSslConnection conn = null;
        SocketChannel socket = null;
        
        try
        {
            socket = server.accept();
            socket.configureBlocking(true);
            socket.setOption(StandardSocketOptions.TCP_NODELAY, true);
            if (soLingerS >= 0)
            {//默认关闭连接时最长保留20秒时间用于发送未发送完的数据
                socket.setOption(StandardSocketOptions.SO_LINGER, soLingerS);
            }
            socket.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
            socket.setOption(StandardSocketOptions.SO_RCVBUF, soRecvBuf);
            socket.setOption(StandardSocketOptions.SO_SNDBUF, soSendBuf);
            
            //先默认阻塞，并检查握手
            conn = newConnection(socket);
            if (!ssl.doHandshake(conn))
            {//SSL的握手失败
                conn.close();
                return;
            }
            
            //握手成功置为非阻塞，注册读事件
            socket.configureBlocking(false);
            socket.register(selector, SelectionKey.OP_READ, add(conn));
        }
        catch (Throwable e)
        {
            if (conn != null)
            {//连接创建成功异常，关连接
                conn.close();
            }
            else if (socket != null)
            {//Socket异常，关Socket
                Closes.closeIgnoreException(socket);
            }
        }
    }
    
    @Override /** 新建封装的连接对象 */
    protected HttpNioSslConnection newConnection(SocketChannel socket) throws IOException
    {
        InetSocketAddress addr = (InetSocketAddress)socket.getRemoteAddress();
        
        SSLEngine engine = sslContext.createSSLEngine();
        engine.setUseClientMode(ssl.isClientAuth());
        engine.setWantClientAuth(ssl.isClientAuth());
        engine.setEnableSessionCreation(true);
        
        return new HttpNioSslConnection(this, socket, addr.getAddress().getHostAddress(), addr.getPort(), engine);
    }
}
